﻿// <copyright file="EnOceanLogger.cs" company="Hallvard Korsgaard">
//  Microsoft Public License (Ms-PL). See license details here: http://enoceannet.codeplex.com/license
// </copyright>
// <author>Hallvard Korsgaard</author>

namespace SqlLogger
{
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Net;
  using System.ServiceProcess;
  using System.Text;
  using EnOcean;
  using EnOcean.Message;
  using EnOcean.Radio.Sensor;
  using SqlLogger.DataAccess;
  using StcEthernet;
  using StcEthernet.Management;

  /// <summary>
  /// The Windows service.
  /// </summary>
  internal partial class EnOceanLogger : ServiceBase
  {
    /// <summary>
    /// This variable is used for synchronizing write access to the console.
    /// </summary>
    private readonly object lck = new object();

    /// <summary>
    /// Used for keeping track of the transievers so we can clean up before exiting.
    /// </summary>
    private readonly List<Transiever> transievers = new List<Transiever>();

    /// <summary>
    /// The server that receives events form the STC-Ethernet transiever.
    /// </summary>
    private Server server;

    /// <summary>
    /// Initializes a new instance of the EnOceanLogger class.
    /// </summary>
    public EnOceanLogger()
    {
      this.InitializeComponent();
    }

    /// <summary>
    /// Called by the service runtime to start the service.
    /// </summary>
    /// <param name="args">Serivce arguments.</param>
    protected override void OnStart(string[] args)
    {
      IPAddress serverAddress;
      IPEndPoint serverEndpoint;

      try
      {
        // Create a new server listening on TCP port 4000.
        serverAddress = IPAddress.Parse(Properties.Settings.Default.ServerIPAddress);
        serverEndpoint = new IPEndPoint(serverAddress, Properties.Settings.Default.ServerPort);
        this.server = new Server(serverEndpoint);

        // Hook up the event handlers.
        this.server.TransieverConnected += new EventHandler<TransieverConnectedEventArgs>(this.TransieverConnected);
        this.server.TransieverInitialized += new EventHandler<TransieverInitializedEventArgs>(this.TransieverInitialized);

        // Start to accept connections.
        this.server.Accept();
      }
      catch (Exception e)
      {
        EventLog.WriteEntry(e.ToString(), EventLogEntryType.Error);
      }
    }

    /// <summary>
    /// Called by the service runtime to stop the service.
    /// </summary>
    protected override void OnStop()
    {
      try
      {
        // Stop the server
        this.server.Close();

        // Close Transiever objects
        lock (this.transievers)
        {
          foreach (Transiever t in this.transievers)
          {
            t.Close();
          }
        }
      }
      catch (Exception e)
      {
        EventLog.WriteEntry(e.ToString(), EventLogEntryType.Error);
      }
    }

    /// <summary>
    /// This event handler will be called when a transiever connects.
    /// The event handler code must be thread safe because several events can fire at the same time.
    /// </summary>
    /// <param name="sender">The object that sent the event.</param>
    /// <param name="e">The event data.</param>
    private void TransieverConnected(object sender, TransieverConnectedEventArgs e)
    {
      EventLog.WriteEntry("Transiever connected.", EventLogEntryType.Information);

      lock (this.transievers)
      {
        this.transievers.Add(e.Transiever);
      }
    }

    /// <summary>
    /// This event handler will be called when the Transiever object is ready for use.
    /// The event handler code must be thread safe because several events can fire at the same time.
    /// </summary>
    /// <param name="sender">The object that sent the event.</param>
    /// <param name="e">The event data.</param>
    private void TransieverInitialized(object sender, TransieverInitializedEventArgs e)
    {
      StringBuilder sb;

      sb = new StringBuilder();

      sb.AppendLine("Transiever initialized.");
      sb.AppendLine("Base ID: " + e.Transiever.BaseId.ToString("X8"));
      sb.AppendLine("Modem on: " + e.Transiever.IsModemOn);
      sb.AppendLine("Modem ID: " + e.Transiever.ModemId.ToString("X4"));
      sb.AppendLine("Operating mode: " + e.Transiever.OperatingMode);
      sb.AppendLine("Radio sensitivity: " + e.Transiever.RadioSensitivity);
      sb.AppendLine("Software version: " + e.Transiever.SoftwareVersion);

      EventLog.WriteEntry(sb.ToString(), EventLogEntryType.Information);

      // Hooking up event handlers.
      e.Transiever.TelegramReceived += new EventHandler<TelegramReceivedEventArgs>(this.Transiever_TelegramReceived);
      e.Transiever.ManagementTelegramReceived += new EventHandler<ManagementTelegramReceivedEventArgs>(this.Transiever_ManagementTelegramReceived);
      e.Transiever.UnknownTelegramReceived += new EventHandler<UnknownDataReceivedEventArgs>(this.Transiever_UnknownTelegramReceived);
      e.Transiever.ResponseTimeout += new EventHandler<ResponseTimeoutEventArgs>(this.Transiever_ResponseTimeout);
      e.Transiever.TransieverDisconnected += new EventHandler<TransieverDisconnectedEventArgs>(this.Transiever_TransieverDisconnected);
    }

    /// <summary>
    /// The event fires when a telegram is received from the transiever.
    /// The event handler code must be thread safe because several events can fire at the same time.
    /// </summary>
    /// <param name="sender">The object that sent the event.</param>
    /// <param name="e">The event data.</param>
    private void Transiever_TelegramReceived(object sender, TelegramReceivedEventArgs e)
    {
      try
      {
        if (e.Telegram is InformIdBase)
        {
          // DisplayInformIdBase((InformIdBase)e.Telegram);
        }
        else if (e.Telegram is InformInit)
        {
          // DisplayInformInit((InformInit)e.Telegram);
        }
        else if (e.Telegram is InformModemStatus)
        {
          // DisplayInformModemStatus((InformModemStatus)e.Telegram);
        }
        else if (e.Telegram is InformRadioSensitivity)
        {
          // DisplayInformRadioSensitivity((InformRadioSensitivity)e.Telegram);
        }
        else if (e.Telegram is InformSoftwareVersion)
        {
          // DisplayInformSoftwareVersion((InformSoftwareVersion)e.Telegram);
        }
        else if (e.Telegram is RepeatedSwitchNTelegram)
        {
          this.LogRepeatedSwitchNTelegram((RepeatedSwitchNTelegram)e.Telegram, e.Channel);
        }
        else if (e.Telegram is RepeatedSwitchUTelegram)
        {
          this.LogRepeatedSwitchUTelegram((RepeatedSwitchUTelegram)e.Telegram, e.Channel);
        }
        else if (e.Telegram is OneByteSensorTelegram)
        {
          // DisplayOneByteSensorTelegram((OneByteSensorTelegram)e.Telegram, e.Channel);
        }
        else if (e.Telegram is FourByteSensorDataTelegram)
        {
          this.LogFourByteSensorDataTelegram((FourByteSensorDataTelegram)e.Telegram, e.Channel);
        }
        else if (e.Telegram is FourByteSensorTeachTelegram)
        {
          // DisplayFourByteSensorTeachTelegram((FourByteSensorTeachTelegram)e.Telegram, e.Channel);
        }
      }
      catch (Exception ex)
      {
        EventLog.WriteEntry(ex.ToString(), EventLogEntryType.Error);
      }
    }

    /// <summary>
    /// The event fires when a management telegram is received from the transiever.
    /// The event handler code must be thread safe because several events can fire at the same time.
    /// </summary>
    /// <param name="sender">The object that sent the event.</param>
    /// <param name="e">The event data.</param>
    private void Transiever_ManagementTelegramReceived(object sender, ManagementTelegramReceivedEventArgs e)
    {
      lock (this.lck)
      {
        Console.WriteLine();
        Console.WriteLine(DateTime.Now);
        Console.WriteLine("Management telegram: " + e.Telegram.GetType().ToString() + " " + e.Telegram);

        if (e.Telegram is InformMode)
        {
          // DisplayInformMode((InformMode)e.Telegram);
        }
        else if (e.Telegram is InformTimeError)
        {
          // DisplayInformTimeError((InformTimeError)e.Telegram);
        }
      }
    }

    /// <summary>
    /// This event handler will be called when if unrecognizable information is received.
    /// The event handler code must be thread safe because several events can fire at the same time.
    /// </summary>
    /// <param name="sender">The object that sent the event.</param>
    /// <param name="e">The event data.</param>
    private void Transiever_UnknownTelegramReceived(object sender, UnknownDataReceivedEventArgs e)
    {
      StringBuilder sb;

      sb = new StringBuilder();

      sb.AppendLine("Unknown telegram: " + Encoding.ASCII.GetString(e.Data));
      sb.AppendLine(e.Exception.ToString());

      EventLog.WriteEntry(sb.ToString(), EventLogEntryType.Warning);
    }

    /// <summary>
    /// This event handler will be called if an expected response message is not received when sending a message.
    /// The event handler code must be thread safe because several events can fire at the same time.
    /// </summary>
    /// <param name="sender">The object that sent the event.</param>
    /// <param name="e">The event data.</param>
    private void Transiever_ResponseTimeout(object sender, ResponseTimeoutEventArgs e)
    {
      lock (this.lck)
      {
        Console.WriteLine();
        Console.WriteLine(DateTime.Now);
        Console.WriteLine("Response timeout.");
      }
    }

    /// <summary>
    /// This event handler will be called when a transiever disconnects.
    /// The event handler code must be thread safe because several events can fire at the same time.
    /// </summary>
    /// <param name="sender">The object that sent the event.</param>
    /// <param name="e">The event data.</param>
    private void Transiever_TransieverDisconnected(object sender, TransieverDisconnectedEventArgs e)
    {
      EventLog.WriteEntry("Transiever disconnected.");

      lock (this.transievers)
      {
        this.transievers.Remove(e.Transiever);
      }

      // The Transiever object will call the Close method on itself after this event handler returns.
    }

    /// <summary>
    /// Prints a FourByteSensorDataTelegram telegram to the console.
    /// </summary>
    /// <param name="t">A FourByteSensorDataTelegram telegram.</param>
    /// <param name="channel">The channel that received the telegram or 0xFF if in gateway mode.</param>
    private void LogFourByteSensorDataTelegram(FourByteSensorDataTelegram t, byte channel)
    {
      DateTimeOffset now;

      now = DateTimeOffset.Now;

      // TODO: Update the IDs below with you own sensor IDs. The ConvertRange method is used for converting raw sensor data to real world values.
      if (t.Id == 0x00020011)
      {
        // Temp & Humidity sensor.
        Database.StoreAnalogSensor(t.Id, SensorType.Temperature, now, t.Data1, Telegram.ConvertRange(t.Data1, 0, 250, 0, 40));
        Database.StoreAnalogSensor(t.Id, SensorType.Humidity, now, t.Data2, Telegram.ConvertRange(t.Data2, 0, 250, 0, 100));
      }
      else if (t.Id == 0x00020022)
      {
        Database.StoreAnalogSensor(t.Id, SensorType.Temperature, now, t.Data1, Telegram.ConvertRange(t.Data1, 255, 0, -20, 60));
      }
      else if (t.Id == 0x0001021D)
      {
        // Motion & Light sensor
        Database.StoreDigitalSensor(t.Id, SensorType.Motion, now, (t.Data0 & 0x2) == 0x0);
        Database.StoreAnalogSensor(t.Id, SensorType.Illuminance, now, t.Data2, Telegram.ConvertRange(t.Data2, 0, 255, 0, 512));
        Database.StoreAnalogSensor(t.Id, SensorType.Voltage, now, t.Data3, Telegram.ConvertRange(t.Data3, 0, 255, 0, 5.12m));
      }
      else if (t.Id == 0x000142F6)
      {
        // Outdoor light sensor.
        Database.StoreAnalogSensor(t.Id, SensorType.Illuminance, now, t.Data2, Telegram.ConvertRange(t.Data2, 0, 255, 300, 30000));
      }
    }

    /// <summary>
    /// Prints a RepeatedSwitchNTelegram telegram to the console.
    /// </summary>
    /// <param name="t">A RepeatedSwitchNTelegram telegram.</param>
    /// <param name="channel">The channel that received the telegram or 0xFF if in gateway mode.</param>
    private void LogRepeatedSwitchNTelegram(RepeatedSwitchNTelegram t, byte channel)
    {
      DateTimeOffset now;

      now = DateTimeOffset.Now;

      Database.StoreSwitch(t.Id, now, t.IsEnergyBowPressed, t.FirstButtonId, t.FirstButtonState == ButtonState.I ? true : false);

      if (t.HasSecondAction)
      {
        Database.StoreSwitch(t.Id, now, t.IsEnergyBowPressed, t.SecondButtonId, t.SecondButtonState == ButtonState.I ? true : false);
      }
    }

    /// <summary>
    /// Prints a RepeatedSwitchUTelegram telegram to the console.
    /// </summary>
    /// <param name="t">A RepeatedSwitchUTelegram telegram.</param>
    /// <param name="channel">The channel that received the telegram or 0xFF if in gateway mode.</param>
    private void LogRepeatedSwitchUTelegram(RepeatedSwitchUTelegram t, byte channel)
    {
      Database.StoreSwitch(t.Id, DateTimeOffset.Now, t.IsEnergyBowPressed, t.Buttons, null);
    }
  }
}
